#include "StdAfx.hpp"
#include "ShaderFractals.hpp"
#include "Framework.hpp"
#include "D3dUtils.hpp"
#include "Multishader.hpp"
#include "D3dResources.hpp"
#include "Texture.hpp"
#include "ShaderConstants.hpp"

using namespace common;

namespace ShaderFractal {


////////////////////////////////////////////////////////////////////////////////
// class Screen

Screen::Screen()
: m_FirstRender(true)
{
}

Screen::~Screen()
{
	g_FileChangeMonitor->UnregisterAllFilesByListener(this);
}

void Screen::Init()
{
	m_Camera.KeyboardPanSpeed = frame::GetBackBufferSize().y;

	RelativeToAbsolutePath(&m_FilePath, frame::DataDir, "ShaderFractal01");
	g_FileChangeMonitor->RegisterFile(this, m_FilePath);

	NewFractal();
	ResetView();
}

bool Screen::OnMouseMove( const common::VEC2 &Pos )
{
	if (true)
		return m_Camera.OnMouseMove(Pos);
	else
		return false;
}

bool Screen::OnMouseButton( const common::VEC2 &Pos, frame::MOUSE_BUTTON Button, frame::MOUSE_ACTION Action )
{
	if (true)
		return m_Camera.OnMouseButton(Pos, Button, Action);
	else
		return false;
}

bool Screen::OnMouseWheel( const common::VEC2 &Pos, float Delta )
{
	if (true)
		return m_Camera.OnMouseWheel(Pos, Delta);
	else
		return false;
}

void Screen::Update()
{
	m_Camera.Update();
}

void Screen::Render3d()
{
	D3DPERF_EVENT(L"Shader Fractal");

	bool allOk = m_Effect.get() && m_Effect->IsLoaded();
	for (size_t i = 0; allOk && i < m_ShaderConstants.size(); i++)
		allOk = allOk && m_ShaderConstants[i]->IsLoaded();

	if (allOk)
	{
		RECTF screenPosRect = RECTF(VEC2_ZERO, frame::GetBackBufferSize());
		RECTF screenTexCoordRect;
		m_Camera.ScreenToUnits(screenTexCoordRect.Min, VEC2_ZERO);
		m_Camera.ScreenToUnits(screenTexCoordRect.Max, frame::GetBackBufferSize());

		VERTEX_R2 fullScreenQuad[4] = {
			VERTEX_R2(VEC4(screenPosRect.Min.x, screenPosRect.Min.y, 0.5f, 1.f), VEC2(screenTexCoordRect.Min.x, screenTexCoordRect.Min.y)),
			VERTEX_R2(VEC4(screenPosRect.Max.x, screenPosRect.Min.y, 0.5f, 1.f), VEC2(screenTexCoordRect.Max.x, screenTexCoordRect.Min.y)),
			VERTEX_R2(VEC4(screenPosRect.Min.x, screenPosRect.Max.y, 0.5f, 1.f), VEC2(screenTexCoordRect.Min.x, screenTexCoordRect.Max.y)),
			VERTEX_R2(VEC4(screenPosRect.Max.x, screenPosRect.Max.y, 0.5f, 1.f), VEC2(screenTexCoordRect.Max.x, screenTexCoordRect.Max.y)),
		};

		frame::Dev->SetFVF(VERTEX_R2::FVF);
		
		ID3DXEffect *eff = m_Effect->GetEffect();
		assert(eff);
		
		if (!m_Technique.empty())
			eff->SetTechnique(m_Technique.c_str());

		ERR_GUARD_DIRECTX( eff->SetFloat("g_Time", frame::Timer2.GetTime().ToSeconds_f()) );

		for (size_t i = 0; i < m_ShaderConstants.size(); i++)
			m_ShaderConstants[i]->SetInEffect(eff, m_FirstRender);

		UINT fooPassCount;
		ERR_GUARD_DIRECTX( eff->Begin(&fooPassCount, 0) );
		ERR_GUARD_DIRECTX( eff->BeginPass(0) );

		frame::Dev->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, fullScreenQuad, sizeof(VERTEX_R2));
		frame::RegisterDrawCall(2);

		eff->EndPass();
		eff->End();

		m_FirstRender = false;
	}
	else
		frame::Dev->Clear(0, NULL, D3DCLEAR_TARGET, 0xff000000, 1.f, 0);

	/*
	if (m_Texture.get() == NULL)
		return;

	VEC2 textureSize = VEC2((float)m_TextureSize.x, (float)m_TextureSize.y);
	RECTF rect;
	m_Camera.UnitsToScreen(rect.Min, VEC2_ZERO);
	m_Camera.UnitsToScreen(rect.Max, textureSize);
	rect += VEC2(-0.5f, -0.5f); // Correction for pixel perfection

	RenderTexturePreview(rect, m_Texture.get());*/
}

void Screen::ResetView()
{
	CalcLinearFactors(m_Camera.Scale, m_Camera.Bias.y, -2.f, 2.f, 0.f, frame::GetBackBufferSize().y);
	m_Camera.Bias.x = frame::GetBackBufferSize().x * 0.5f;
}

void Screen::NewFractal()
{
	m_ErrorMessage.clear();
	m_FirstRender = true;

	try
	{
		common::tokdoc::Node doc;
		{
			LOG(LOG_APP, "Loading fractal description from \"" + m_FilePath + "\".");
			FileStream fileStream(m_FilePath, FM_READ);
			Tokenizer tokenizer(&fileStream, Tokenizer::FLAG_MULTILINE_STRINGS);
			tokenizer.Next();
			doc.LoadChildren(tokenizer);
		}

		MakeFractal(doc);
	}
	catch (const Error &e)
	{
		e.GetMessage_(&m_ErrorMessage);
		//m_Texture.reset();
	}
}

void Screen::Render2d()
{
	/*if (!m_ErrorMessage.empty())
	{
		using namespace gfx2d;

		assert(GetMainFont());
		
		g_Canvas->SetFont(GetMainFont());
		g_Canvas->SetColor(COLOR_RED);
		g_Canvas->DrawText_(
			frame::GetBackBufferSize()*0.5f,
			m_ErrorMessage,
			0.f,
			Font::FLAG_HCENTER | Font::FLAG_VMIDDLE | Font::FLAG_WRAP_WORD | Font::FLAG_PIXEL_PERFECT,
			frame::GetBackBufferSize().x);
	}*/
}

bool Screen::OnKeyDown( uint4 Key )
{
	switch (Key)
	{
	case 'F':
		ResetView();
		return true;
	default:
		return false;
	}
}

void Screen::OnFileChange( uint FileId )
{
	NewFractal();
}

void Screen::MakeFractal( common::tokdoc::Node &doc )
{
	using namespace common::tokdoc;

	m_ShaderConstants.clear();

	Node *shaderNode = &doc.MustFindFirstChild("Shader");
	Node *shaderFileNode = &shaderNode->MustFindFirstChild("File");
	Node *shaderCacheNode = shaderNode->FindFirstChild("Cache");

	string filePath, cacheFilePath;
	RelativeToAbsolutePath(&filePath, frame::DataDir, shaderFileNode->Value);
	if (shaderCacheNode && !shaderCacheNode->Value.empty())
		RelativeToAbsolutePath(&cacheFilePath, frame::DataDir, shaderCacheNode->Value);

	m_Effect.reset(new D3dEffect(filePath, cacheFilePath));

	Node *shaderTechniqueNode = shaderNode->FindFirstChild("Technique");
	if (shaderTechniqueNode)
		m_Technique = shaderTechniqueNode->Value;
	else
		m_Technique.clear();

	Node *shaderMacrosNode = shaderNode->FindFirstChild("Macros");
	if (shaderMacrosNode)
		for (Node *macroNode = shaderMacrosNode->GetFirstChild(); macroNode; macroNode = macroNode->GetNextSibling())
			m_Effect->AddMacro(macroNode->Name, macroNode->Value);

	m_Effect->EnableFileChangeMonitoring(ResourceObject::STATE_LOADING);
	m_Effect->BeginLoading();

	Node *constantsNode = shaderNode->FindFirstChild("Constants");
	if (constantsNode)
	{
		for (Node *constantNode = constantsNode->GetFirstChild(); constantNode; constantNode = constantNode->GetNextSibling())
		{
			if (constantNode->Value == "bool")
				m_ShaderConstants.push_back(shared_ptr<ShaderConstant>(new BoolShaderConstant(constantNode)));
			else if (constantNode->Value == "int")
				m_ShaderConstants.push_back(shared_ptr<ShaderConstant>(new IntShaderConstant(constantNode)));
			else if (constantNode->Value == "float")
				m_ShaderConstants.push_back(shared_ptr<ShaderConstant>(new FloatShaderConstant(constantNode)));
			else if (constantNode->Value == "float4")
				m_ShaderConstants.push_back(shared_ptr<ShaderConstant>(new Float4ShaderConstant(constantNode)));
			else if (constantNode->Value == "float4x4")
				m_ShaderConstants.push_back(shared_ptr<ShaderConstant>(new Float4x4ShaderConstant(constantNode)));
			else if (constantNode->Value == "texture")
				m_ShaderConstants.push_back(shared_ptr<ShaderConstant>(new TextureShaderConstant(constantNode)));
			else
				LOG(LOG_APP | LOG_WARNING, "Unknown shader constant type \"" + constantNode->Value + "\".");
		}
	}
}

} // namespace ShaderFractal
